/*
    Copyright 2010 Barnet Wagman

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

package com.norbl.util;

import java.text.*;

/** A (name,value) pair.  Unlike Java system properties, 
 *  a <tt>Property</tt>'s
 *  value is an <tt>Object</tt> not a <tt>String</tt>.
 *
 * @author Barnet Wagman
 */
public class Property implements Comparable {

    private static NumberFormat numberFormat = NumberFormat.getInstance();

    public String name;
    public Object value;

        /** If the value is a <tt>String</tt>, it will be
         *  interpreted as follows:
         *  <ul>
         *  <li>"null" -> <tt>null</tt></li>
         *  <li>"true"|"false" -> the corresponding <tt>Boolean</tt>.
         *  <li>a number -> <tt>Number</tt>. Note
         *      that a a string numerals ending in 'L' i
         *      treated as a <tt>Long</tt>.
         *  </ul>
         *
         * @param name
         * @param value any <tt>Object</tt> or <tt>null</tt>.
         */
    public Property(String name, Object value) {
        this.name = name;
        this.value = parseString(value);
    }

        /** Creates a <tt>Property</tt> from a string that contains
         *  a name/value pair.  The string must have the form
         *  <blockquote>
         *      <tt>&lt;name&gt;=&lt;value&gt;</tt><p>
         *      E.g.<br><tt>
         *      AWSAccessKeyID=your-access-key<br>
         *      SecretAccessKey=your-secret-access-key</tt>
         *  </blockquote>
         *  to be parsed; otherwise <tt>null</tt> is returned.<p>
         *  If the value is a <tt>String</tt>, it is parsed by
         *  {@link #parseString(java.lang.Object)}.
         * @param s
         * @return <tt>Property</tt> containing the contents of the string
         *  or null the string does not contain a properly formatted
         *  name,value pair.
         */
    public static Property parsePair(String s) {

        String st = s.trim();
        if ( !st.contains("=") ) return(null);

        String[] sts = st.split("=",2);
        if ( sts.length != 2) return(null);

        return(new Property(sts[0],parseString(sts[1])));   
    }

        /** If the value is a <tt>String</tt>, it will be
         *  interpreted as follows:
         *  <ul>
         *  <li>"null" -> <tt>null</tt></li>
         *  <li>"true"|"false" -> the corresponding <tt>Boolean</tt>.
         *  <li>a number -> <tt>Number</tt>. Note
         *      that a a string numerals ending in 'L' is
         *      treated as a <tt>Long</tt>.
         *  </ul>
         *
         *  Otherwise the object will be returned unchanged.
         */
    public static Object parseString(Object val) {
        if ( !(val instanceof String) ) return(val);
        else {
            String s = (String) val;
            if ( s.equals("null") ) return(null);
            else if ( s.equals("true") || s.equals("false") )
                return(Boolean.parseBoolean(s));
            else if ( isNumber(s) || isLong(s) ) {
                try {
                    Number num = numberFormat.parse(s);
                    if ( num != null ) {
                        if ( num instanceof Long ) {
                            if ( isLong(s) ) return(num);
                            else return( new Integer(num.intValue()) );
                        }
                        else return(num);
                    }
                    else return(s);
                }
                catch(ParseException px ) {
                    return(s);
                }
            }
            else return(s);
        }        
    }

    public String toPairString() {
        if ( value != null )
            return(name + "=" + valueToString());
        else return(name + "=" + "null");
    }

        /** A <tt>Long</tt> is written with a trailng 'L'
         * 
         * @return value expressed as String; <tt>null</tt> yields "null".
         */
    public String valueToString() {
        if ( value == null ) return("null");
        if ( value instanceof Long )
            return( ((Long) value).toString() + "L" );

        if ( value != null ) return(value.toString());
        else return("null");
    }

    public int compareTo(Object other) {
        Property o = (Property) other;
        return(this.name.compareTo(o.name));
    }

    private static boolean isNumber(String str) {
        String s = str.trim();
        char s0 = s.charAt(0);
        if ( !Character.isDigit(s0) && (s0 != '-') ) return(false);
        for ( int i = 1; i < s.length(); i++ ) {
            if ( !Character.isDigit(s.charAt(i)) ) return(false);
        }
        return(true);
    }

    private static boolean isLong(String s) {
        if ( s.charAt(s.length()-1) != 'L' ) return(false);
        else return( isNumber(s.substring(0,s.length()-1)) );
    }
}
